-- Modified by Alundaio local pi_2 = math.pi / 3 -- 60 degree local def_min_delta_per_sec = 0.2 local def_min_car_explode_time = 1000 local def_fire_angle = 120 local def_min_fire_time = 1.0 local def_fire_rep = 0.5 local def_fire_range = 50 local def_max_fc_upd_num = 1 -- default maximum fastcall updates num local state_cannon_rotate = 1 local state_cannon_follow = 2 local state_cannon_delay = 3 local state_cannon_stop = 4 local state_shooting_on = 1 local state_none = 0 local state_firetarget_points = 1 local state_firetarget_enemy = 2 class "action_mgun" function action_mgun:__init(obj, storage) -- printf("mgun : init.") self.object = obj self.mgun = self.object:get_car() self.st = storage self.start_direction = self.object:direction() self.start_look_pos = VEC_ZERO self.start_look_pos.x = self.object:position().x + 5*math.sin(self.start_direction.x) self.start_look_pos.z = self.object:position().z + 5*math.cos(self.start_direction.x) self.start_look_pos.y = self.object:position().y end function action_mgun:reset_scheme(loading) --utils_data.debug_write("action_mgun:reset_scheme") -- printf("car : START INITIALIZING ======================================================") -- printf("car : action_mgun:reset_scheme: self.object:name()='%s'", self.object:name()) self.start_delaying_time = time_global() self.start_shooting_time = time_global() -- printf("pl mgun direction x:--- %s , z:--- %s", tostring(self.start_direction.x), tostring(self.start_direction.z)) self.fc_upd_num = 0 -- fastcall updates num self.fc_upd_avg = 10 -- average time of the fastcall updates (in millisecond) self.fc_last_upd_tm = -1 -- fastcall last update time self.st.signals = {} self.st.owner_id = nil self.st.target_id = nil self.last_pos = nil self.last_pos_time = 0 self.state_delaying = false self.destroyed = false self.object:set_nonscript_usable(false) self.object:set_tip_text("") if self.mgun:HasWeapon() then -- printf("car : car has weapon.") self.mgun:Action(CCar.eWpnActivate,1) self.hasWeapon = true else -- printf("car : car hasn't weapon.") self.hasWeapon = false end self.state_firetarget = state_none self.state_cannon = state_none self.state_shooting = state_none self.target_fire_pt = nil self.target_fire_pt_idx = 0 self.on_target_vis = nil self.on_target_nvis = nil if self.hasWeapon then --printf("car : target = %s", self.st.fire_target) local n = 0 if self.st.fire_target == "points" then self.state_firetarget = state_firetarget_points elseif (self.st.fire_target ~= "job") then if self.st.fire_target == "actor" and db.actor:alive() then self.st.target_id = 0 self.state_firetarget = state_firetarget_enemy else local obj = get_story_object(self.st.fire_target) if obj and obj:alive() then self.st.target_id = obj:id() self.state_firetarget = state_firetarget_enemy end end end self.fire_track_target = self.st.fire_track_target if self.st.on_target_vis then vis = self.st.on_target_vis if vis.v1 == "actor" then vis.v1 = 0 self.on_target_vis = vis -- printf("car : target actor") elseif (self.st.fire_target ~= "job") then local obj = get_story_object(vis.v1) if obj and obj:alive() then vis.v1 = obj:id() self.on_target_vis = vis -- printf("car : target %d", n) end end end if self.st.on_target_nvis then nvis = self.st.on_target_nvis if nvis.v1 == "actor" then nvis.v1 = 0 self.on_target_nvis = nvis -- printf("car : target actor") elseif (self.st.fire_target ~= "job") then local obj = get_story_object(nvis.v1) if obj and obj:alive() then nvis.v1 = obj:id() self.on_target_nvis = nvis -- printf("car : target %d", n) end end end self.path_fire = self.st.path_fire self.path_fire_point = nil --' Запоминаем позицию куда стрелять. if self.path_fire ~= nil then if level.patrol_path_exists(self.path_fire) then self.path_fire_point = patrol(self.path_fire):point(0) else printf("[ph_minigun] patrol path %s doesnt exist.", tostring(self.path_fire)) end end self.def_fire_time = self.st.fire_time self.def_fire_rep = self.st.fire_rep self.fire_rep = self.def_fire_rep -- printf("car : def_rep = %d (%s)", self.fire_rep, utils_data.to_str(self.st.fire_rep)) self.fire_range_sqr = self.st.fire_range * self.st.fire_range --' Стрельба по патрульным точкам. if self.state_firetarget == state_firetarget_points and self.path_fire then -- printf("car : firetarget = points") self.state_cannon = state_cannon_follow self.state_shooting = state_none elseif self.state_firetarget == state_firetarget_enemy then -- printf("car : firetarget = enemy") self.state_shooting = state_none self.state_cannon = state_cannon_follow else -- printf("car : firetarget = none") self.state_firetarget = state_none self.state_cannon = state_none self.state_shooting = state_none end end self.object:set_fastcall(self.fastcall, self) -- printf("car : END INITIALIZING ========================================================\n") end function action_mgun:set_shooting(shooting) self.mgun:Action(CCar.eWpnFire, shooting) --'printf("car : action_car:set_shooting(%d)", shooting) end function action_mgun:check_fire_time() if self.st.fire_rep == -1 then return end local tg = time_global() if 1000*self.st.fire_time + self.start_shooting_time >= tg and self.state_delaying == false then self.state_delaying = false self.start_delaying_time = tg + math.random(-0.2, 0.2)*1000*self.st.fire_time return else self.state_delaying = true end if self.start_delaying_time + 1000*self.st.fire_rep >= tg and self.state_delaying == true then self.state_delaying = true self.start_shooting_time = tg else self.state_delaying = false end end function action_mgun:save() end function action_mgun:rot_to_firedir(direction) if direction then self.mgun:SetParam(CCar.eWpnDesiredPos, direction) end end function action_mgun:rot_to_firepoint(pt) if pt then self.mgun:SetParam(CCar.eWpnDesiredPos, pt) end end function action_mgun:set_signal(sig) local stor = db.storage[self.object:id()] stor[stor.active_scheme].signals[sig] = true -- printf("car : %s", sig) end function action_mgun:fastcall() if db.storage[self.object:id()].active_scheme ~= "ph_minigun" then --' Если активная схема - не машина, снять быстрый апдейт self:set_shooting(0) return true end return self:fast_update() end local function find_valid_target(mgun,npc,st) --utils_data.debug_write("action_mgun:find_valid_target") if (IsWounded(npc)) then return end local nst = db.storage[npc:id()] local target = nst and nst.enemy_id and (db.storage[nst.enemy_id] and db.storage[nst.enemy_id].object or level.object_by_id(nst.enemy_id)) if (target and target:alive() and not xr_combat_ignore.ignore_enemy_by_overrides(npc,target,true)) then return nst.enemy_id end end function action_mgun:update(delta) --utils_data.debug_write("action_mgun:update") if xr_logic.try_switch_to_another_section(self.object, self.st, db.actor) then return end if self.destroyed then xr_logic.switch_to_section(self.object, self.st.ini, "nil") return end -- get target of npc on job if (self.st.fire_target == "job") then local owner = self.st.smart and self.st.job and self.st.smart.npc_by_job_section[self.st.job] if (owner) then local st = db.storage[owner] if (st) then if (st.rx_dont_shoot) then printf("action_mgun rx_dont_shoot") self.st.target_id = nil self.state_shooting = state_none self:set_shooting(self.state_shooting) else owner = st.object or level.object_by_id(owner) if (owner and owner:alive()) then self.st.owner_id = owner:id() local tar = find_valid_target(self.mgun,owner,self.st) if (tar) then self.st.target_id = tar self.state_firetarget = state_firetarget_enemy if (self.st.on_target_vis) then self.st.on_target_vis.v1 = tar self.on_target_vis = self.st.on_target_vis end if (self.st.on_target_nvis) then self.st.on_target_nvis.v1 = tar self.on_target_nvis = self.st.on_target_nvis end end --actor_menu.set_msg(1, strformat("minigunner(%s): target=%s",owner:name(),self.st.target_obj and self.st.target_obj:name())) end end end end end self:check_fire_time() end function action_mgun:fast_update() --printf("car : START FAST UPDATE ======================================================") if self.mgun:GetfHealth() <= 0 then -- printf("car : killed.") self:destroy_car() return true end local cur_time = time_global() if self.fc_upd_num < def_max_fc_upd_num then local last_upd = self.fc_last_upd_tm if last_upd ~= -1 then local n = self.fc_upd_num if n < 3000 then self.fc_upd_avg = (self.fc_upd_avg * n + (cur_time - last_upd))/(n + 1) self.fc_upd_num = n + 1 else self.fc_upd_num = 1 end end self.fc_last_upd_tm = cur_time ---- printf("car : average update = %f, time(%f)", self.fc_upd_avg, cur_time) end if (self.state_cannon == state_cannon_stop and self.state_firetarget == state_none) then if xr_logic.mob_captured(self.object) and not self.object:action() then -- printf("car : stopped") self:destroy_car() return true -- апдейты больше не нужны end return false end if not (self.hasWeapon) then return false end -- on_target_vis if (self.on_target_vis and self.on_target_vis.v1) then local v1 = db.storage[self.on_target_vis.v1] and db.storage[self.on_target_vis.v1].object if (v1 and v1:alive()) then if (self.mgun:IsObjectVisible(v1)) or (self.owner and self.owner:see(v1)) then local new_section = xr_logic.pick_section_from_condlist(db.actor, self.object, self.on_target_vis.condlist) if new_section then xr_logic.switch_to_section(self.object, self.st.ini, new_section) end end end end -- on_target_nvis if (self.on_target_nvis and self.on_target_nvis.v1) then local v1 = db.storage[self.on_target_nvis.v1] and db.storage[self.on_target_nvis.v1].object if (v1 and v1:alive()) then if (self.mgun:IsObjectVisible(v1)) or (self.owner and self.owner:see(v1)) then local new_section = xr_logic.pick_section_from_condlist(db.actor, self.object, self.on_target_nvis.condlist) if new_section then xr_logic.switch_to_section(self.object, self.st.ini, new_section) end end end end if self.state_firetarget == state_firetarget_points then --' Стрельба по точкам local fire_angle = angle_xz(self.object, self.path_fire_point, self.start_direction) local can_rotate = fire_angle <= self.st.fire_angle*math.pi/360 and fire_angle >= -(self.st.fire_angle*math.pi/360) if can_rotate then self:rot_to_firepoint(self.path_fire_point) if self.state_delaying then if self.state_shooting ~= state_none and self.st.auto_fire == true then self.state_shooting = state_none self:set_shooting(self.state_shooting) end else if self.state_shooting == state_none then self.state_shooting = state_shooting_on self:set_shooting(self.state_shooting) end end end elseif (self.st.target_id and self.state_firetarget == state_firetarget_enemy) then local target = db.storage[self.st.target_id] and db.storage[self.st.target_id].object if not (target) then return false end local owner = self.st.owner_id and db.storage[self.st.owner_id] and db.storage[self.st.owner_id].object if (self.st.owner_id) and not (owner and owner:alive()) then return false end local alive = target:alive() local fire_angle = angle_xz(self.object, target:position(), self.start_direction) local can_rotate = fire_angle <= self.st.fire_angle*math.pi/360 and fire_angle >= -(self.st.fire_angle*math.pi/360) local object_visible = alive and (self.mgun:IsObjectVisible(target) or owner and owner:see(target) or self.st.shoot_only_on_visible == false) --' -- printf("object visible:---------> %s can_rotate = %s fire_angle = %s", tostring(object_visible), tostring(can_rotate), tostring(fire_angle)) if (alive and object_visible and can_rotate and self.object:position():distance_to_sqr(target:position()) <= self.fire_range_sqr) then if not self.state_delaying then self.target_fire_pt = target:position() self.target_fire_pt.z = self.target_fire_pt.z + (math.random(-1,1)/100) self.target_fire_pt.x = self.target_fire_pt.x + (math.random(-1,1)/100) if (target:id() ~= AC_ID) then if (IsMonster(target) or target:target_body_state() == move.crouch) then self.target_fire_pt.y = self.target_fire_pt.y + 0.5 + (math.random(-1,1)/100) else self.target_fire_pt.y = self.target_fire_pt.y + 0.1 end else self.target_fire_pt.y = self.target_fire_pt.y + 1 + (math.random(-1,1)/100) --' FAKE end self:rot_to_firepoint(self.target_fire_pt) if self.mgun:CanHit() then if self.state_shooting == state_none and self.st.auto_fire == true then --'printf("car : shooting enemy (first).") self.state_shooting = state_shooting_on self:set_shooting(self.state_shooting) end else if self.state_shooting ~= state_none then --'printf("car : targeting enemy (first).") self.state_shooting = state_none self:set_shooting(self.state_shooting) end --'printf("car : targeting enemy.") end else self.state_shooting = state_none self:set_shooting(self.state_shooting) end else if self.state_shooting ~= state_none or not can_rotate or self.state_delaying then --' printf("car : enemy isn't visible (first).%s %s %s", tostring(self.object:direction().x), tostring(self.object:direction().y), tostring(self.object:direction().z)) self.state_shooting = state_none self:set_shooting(self.state_shooting) self:rot_to_firedir(self.start_look_pos) end --' printf("car : enemy isn't visible.") if self.fire_track_target then self.target_fire_pt = target:position() self.target_fire_pt.y = self.target_fire_pt.y + 1 + (math.random(-5,5)/100) -- FAKE self:rot_to_firepoint(self.target_fire_pt) --' printf("car : target tracking.") end end end return false end function action_mgun:destroy_car() --utils_data.debug_write("action_mgun:destroy_car START") -- printf("car : START ===============================") self.state_cannon = state_none self.state_firetarget = state_none self.state_shooting = state_none self.mgun:Action(CCar.eWpnAutoFire, 0) self:set_shooting(self.state_shooting) xr_logic.mob_release(self.object) if self.st.on_death_info ~= nil then db.actor:give_info_portion(self.st.on_death_info) -- printf("car : on_death_info [%s]", self.st.on_death_info) end self.destroyed = true --utils_data.debug_write("action_mgun:destroy_car END") -- printf("car : END =================================") end function angle_xz(npc , target_pos, start_direction) local dir1 = start_direction dir1.y = 0 local dir2 = vector():set(target_pos.x, target_pos.y, target_pos.z) dir2 = dir2:sub(npc:position()) dir2.y = 0 return yaw(dir1, dir2) end --' ****************************************************************************************************************** --' * bind * --' ****************************************************************************************************************** function add_to_binder(npc, ini, scheme, section, storage) ---- printf("DEBUG: add_to_binder: scheme='%s', section='%s'", scheme, section) local new_action = action_mgun(npc, storage) -- Зарегистрировать все actions, в которых должен быть вызван метод reset_scheme при изменении настроек схемы: xr_logic.subscribe_action_for_events(npc, storage, new_action) end function set_scheme(npc, ini, scheme, section, gulag_name) local st = xr_logic.assign_storage_and_bind(npc, ini, scheme, section) st.logic = xr_logic.cfg_get_switch_conditions(ini, section, npc) st.path_fire = utils_data.prefix_r_string(ini,section,"path_fire",gulag_name) st.auto_fire = ini:r_bool_ex(section, "auto_fire",false) st.fire_time = ini:r_float_ex(section,"fire_time") or def_min_fire_time st.fire_rep = ini:r_float_ex(section,"fire_repeat") or def_fire_rep st.fire_range = ini:r_float_ex(section,"fire_range") or def_fire_range st.fire_target = ini:r_string_ex(section,"target") or "points" st.fire_track_target= ini:r_bool_ex(section, "track_target",false) st.fire_angle = ini:r_float_ex(section,"fire_angle") or def_fire_angle st.shoot_only_on_visible = ini:r_bool_ex(section,"shoot_only_on_visible",true) st.on_target_vis = xr_logic.cfg_get_string_and_condlist(ini, section, "on_target_vis", npc) --' Условие перехода st.on_target_nvis = xr_logic.cfg_get_string_and_condlist(ini, section, "on_target_nvis", npc) --' Условие перехода -- Alundaio -- Picks target based on best enemy of smart_terrain job holder st.smart = ini:r_string_ex(section,"smart") if (st.smart) then st.smart = SIMBOARD.smarts_by_names[st.smart] st.job = st.smart and ini:r_string_ex(section,"job") if not (st.job) then st.smart = nil else st.job = "logic@"..st.job end end end